using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class EventManager : MonoBehaviour
{
    public delegate void ChangedLevelEventHandler(object sender, ChangedLevelEventArgs e);

    public delegate void ChangedPlayerEventHandler(object sender, ChangedPlayerEventArgs e);

    public delegate void CollisionEventHandler(object sencer, CollisionEventArgs e);

    public delegate void InputEventHandler(object sender, List<InputEventArgs> e);

    public delegate void LevelFinishedEventHandler(object sender, LevelFinishedEventArgs e);

    public delegate void PassedCheckpointEventHandler(object sender, PassedCheckpointEventArgs e);

    public delegate void PlayerDiedEventHandler(object sender);

    public delegate void PlayerResetEventHandler(object sender, PlayerResetEventArgs e);

    public static string NextLevel;

    private InputDevice currentDevice;
    private int currentLevel;

    public static event ChangedLevelEventHandler OnLevelSelected;
    public static event CollisionEventHandler OnCollision;
    public static event InputEventHandler OnInput;
    public static event ChangedPlayerEventHandler OnPlayerChanged;
    public static event LevelFinishedEventHandler OnLevelFinished;
    public static event PassedCheckpointEventHandler OnPassedCheckpoint;
    public static event PlayerDiedEventHandler OnPlayerDied;
    public static event PlayerResetEventHandler OnPlayerReset;

    private void Start()
    {
        Player.InitializePlayer("Bartolomeus");
        Player.SetObject(GameObject.FindGameObjectWithTag("Player"), this);

        CollisionManager.OnCollison += CollisionManagerOnCollision;
        PlayerManager.OnLevelFinished += PlayerManagerOnLevelFinished;
        PlayerManager.OnPassedCheckpoint += PlayerManagerOnPassedCheckpoint;
        PlayerManager.OnPlayerReset += PlayerManagerOnPlayerReset;
        Player.OnPlayerDied += PlayerOnPlayerDied;

        this.currentDevice = InputDevice.Keyboard;
        this.currentLevel = Application.loadedLevel;

        string[] input = Input.GetJoystickNames();
        foreach (string s in input)
        {
            switch (s)
            {
                case "Controller (XBOX 360 For Windows)":
                    this.currentDevice = InputDevice.XboxController;
                    break;
                case "Controller (PS3 for Windows)":
                    this.currentDevice = InputDevice.Ps3Controller;
                    break;
            }
        }
    }

    private static void PlayerManagerOnPlayerReset(object sender, PlayerResetEventArgs e)
    {
        if (OnPlayerReset != null)
            OnPlayerReset(sender, e);
    }

    private static void PlayerOnPlayerDied()
    {
        if (OnPlayerDied != null)
            OnPlayerDied(typeof(Player));
    }

    private static void PlayerManagerOnPassedCheckpoint(object sender, PassedCheckpointEventArgs e)
    {
        if (OnPassedCheckpoint != null)
            OnPassedCheckpoint(sender, e);
    }

    private static void PlayerManagerOnLevelFinished(object sender, LevelFinishedEventArgs e)
    {
        if (OnLevelFinished != null)
            OnLevelFinished(sender, e);
    }

    private void Update()
    {
        #region setPlayerObject

        switch (Application.isLoadingLevel)
        {
            case false:
                if (this.currentLevel == -1)
                    this.currentLevel = Application.loadedLevel;
                if (Player.Object == null)
                {
                    Player.SetObject(GameObject.FindGameObjectWithTag("Player"), this);
                    if (OnPlayerChanged != null)
                        OnPlayerChanged(this, new ChangedPlayerEventArgs());
                }
                break;
            case true:
                this.currentLevel = -1;
                break;
        }

        #endregion

        #region input

        List<InputEventArgs> inputs = new List<InputEventArgs>();
        switch (this.currentDevice)
        {
            case InputDevice.Keyboard:
                inputs.AddRange(CheckKeyboardInput());
                break;
            case InputDevice.XboxController:
                inputs.AddRange(CheckControllerButtons());
                inputs.AddRange(CheckControllerDPad());
                inputs.AddRange(CheckControllerLeftAnalog());
                break;
            case InputDevice.Ps3Controller:
                inputs.AddRange(CheckControllerButtons());
                inputs.AddRange(CheckControllerDPad());
                inputs.AddRange(CheckControllerLeftAnalog());
                break;
        }

        if (OnInput != null && inputs.Count > 0)
            OnInput(this, inputs);

        #endregion

        #region level

        if (OnLevelSelected != null && NextLevel != "")
        {
            OnLevelSelected(this, new ChangedLevelEventArgs { nextLevel = NextLevel });
            NextLevel = "";
        }

        #endregion
    }

    private void OnDestroy()
    {
        CollisionManager.OnCollison -= CollisionManagerOnCollision;
        PlayerManager.OnLevelFinished -= PlayerManagerOnLevelFinished;
        PlayerManager.OnPassedCheckpoint -= PlayerManagerOnPassedCheckpoint;
        Player.OnPlayerDied -= PlayerOnPlayerDied;
    }

    private void CollisionManagerOnCollision(object sender, CollisionEventArgs e)
    {
        if (OnCollision != null)
            OnCollision(this, e);
    }

    private static List<InputEventArgs> CheckKeyboardInput()
    {
        List<InputEventArgs> inputs = new List<InputEventArgs>();
        List<KeyCode> keys = Enum.GetNames(typeof(KeyCode)).Select(key => (KeyCode)Enum.Parse(typeof(KeyCode), key)).ToList();

        foreach (KeyCode code in keys)
        {
            if (Input.GetKeyDown(code))
            {
                InputEventArgs input = new InputEventArgs
                    {
                        Device = InputDevice.Keyboard,
                        Type = InputType.Down,
                        Input = code.ToString(),
                    };
                inputs.Add(input);
            }
            else if (Input.GetKeyUp(code))
            {
                InputEventArgs input = new InputEventArgs
                    {
                        Device = InputDevice.Keyboard,
                        Type = InputType.Up,
                        Input = code.ToString(),
                    };
                inputs.Add(input);
            }
        }

        return inputs;
    }

    private static List<InputEventArgs> CheckControllerLeftAnalog()
    {
        string[] axes = { "Left Analog Horizontal", "Left Analog Vertical" };

        return (from axis in axes
                let f = Input.GetAxis(axis)
                let prefix = axis.Contains("Vertical") ? "v" : "h"
                select new InputEventArgs
                    {
                        Device = InputDevice.XboxController,
                        Type = InputType.LAnalog,
                        Input = prefix + f
                    }).ToList();
    }

    private static List<InputEventArgs> CheckControllerDPad()
    {
        string[] axes = { "dPad Horizontal", "dPad Vertical" };

        return (from axis in axes
                let prefix = axis.Contains("Vertical") ? "v" : "h"
                let f = Input.GetAxis(axis)
                where f != 0
                select new InputEventArgs
                    {
                        Device = InputDevice.XboxController,
                        Type = InputType.DPad,
                        Input = prefix + f
                    }).ToList();
    }

    private static List<InputEventArgs> CheckControllerButtons()
    {
        string[] buttons = { "A", "B", "X", "Y" };

        List<InputEventArgs> inputs = (from button in buttons
                                       where Input.GetButtonDown(button)
                                       select new InputEventArgs
                                           {
                                               Device = InputDevice.XboxController,
                                               Type = InputType.Down,
                                               Input = button
                                           }).ToList();

        inputs.AddRange(from button in buttons
                        where Input.GetButtonUp(button)
                        select new InputEventArgs
                            {
                                Device = InputDevice.XboxController,
                                Type = InputType.Up,
                                Input = button
                            });

        return inputs;
    }
}

public class InputEventArgs : EventArgs
{
    public InputDevice Device;
    public string Input;
    public InputType Type;
}

public class ChangedLevelEventArgs : EventArgs
{
    public string nextLevel;
}

public class ChangedPlayerEventArgs : EventArgs
{
}

public class LevelFinishedEventArgs : EventArgs
{
    public string Level;
    public int Score;
    public double Time;
}

public class PassedCheckpointEventArgs : EventArgs
{
    public Checkpoint PassedCheckpoint;
}

public enum InputType
{
    Down,
    Up,
    Hold,
    DPad,
    LAnalog
}

public enum CollisionSide
{
    Left,
    Right,
    Up,
    Down,
    None
}

public enum CollisionType
{
    Enter,
    Stay,
    Exit,
    None
}